Java程序员面试宝典(四)

81. 用TCP通信模型创建Web服务器

ISS、Apache、Tomcat等服务器软件可以用来创建Web站点,负责接收客户端浏览器的HTTP请求,其基本原理是采用TCP通信模型。
HTTP是应用层协议,它基于TCP协议,它的底层通信原理遵循TCP通信模型,但HTTP还定义了一些其他的Web通信规范。
如HTTP的状态200表示请求成功,返回的HTML语言的文本等。
服务器端Web应用的默认端口是80,也可修改。

82. 利用UDP通信模型创建即时聊天软件

QQ、MSN等聊天工具对安全的要求不高,一般采用UDP通信模型,UDP的通信是点对点的,不存在谁是服务器,谁是客户端,因此UDP的多线程模型就不用主线程来监听请求了,只需要在线程中分别用DatagramPacket通信即可。
可以创建一个发送消息的线程类和接收消息的线程类。发送消息的线程类主要是监听用户的输入,并通过DatagramPacket类的send()方法把数据发给其他的用户;而接收消息的线程类循环调用DatagramPacket类的receive()方法,接收其他用户发来的数据,并打印在屏幕上。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/*
* 定义两个线程类,一个用于接收ReceiveThread,一个用于发送SendThread
*/
public class Chat {
public static void main(String[] args) throws Exception {
//agrs格式:本用户的接收端口、发送端口、对方的接收端口。传入的是String类型
//启动接收线程
new ReceiveThread(Integer.parseInt(args[0])).start();
//启动发送线程
new SendThread(Integer.parseInt(args[1]),
Integer.parseInt(args[2])).start();
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/*
* 接收线程
*/
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;

public class ReceiveThread extends Thread{
private DatagramSocket ds;
public ReceiveThread(int port) {
// 初始化根据端口号创建DatagramSocket对象
super();
try{
this.ds = new DatagramSocket(port);
} catch (SocketException se){
se.printStackTrace();
}
}
public void run() {
try{
byte[] buff = new byte[1024];
DatagramPacket dp = new DatagramPacket(buff, 1024); //创建数据报对象
while (true) {
ds.receive(dp);
String str = new String(dp.getData(), 0, dp.getLength());
//dp.getData()得到的是byte[]数组中的数据
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
/*
* 发送线程
*/
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;

public class SendThread extends Thread {
private DatagramSocket ds;
private int sendPort;

public SendThread(int port, int sendPort) {
super();
this.sendPort = sendPort;
try {
this.ds = new DatagramSocket(port);
} catch (SocketException se) {
se.printStackTrace();
}
}

public void run() {
try {
// 循环接收用户输入
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String str = null;
while ((str = br.readLine()) != null) {
// 创建DatagramPacket对象
DatagramPacket dp = new DatagramPacket(str.getBytes(), 0, str.length(),
InetAddress.getByName("localhost"), sendPort);
ds.send(dp);
System.out.println("send:" + str);
}
} catch (IOException ioe) {
ioe.printStackTrace();
} finally {
ds.close();
}
}
}

运行上面聊天程序时,需要打开两个应用程序,一个发送消息,可以在另一个上看到消息。

83. 使用Java访问Web站点

网络爬虫软件用于搜索引擎抓取网页,原理就是模拟浏览器挨个去访问Web站点,得到站点网址的映射。
URL又被称为网络地址,它唯一标识了网络上的资源。通过java.net.URL类访问网络资源。
java.net.HttpURLConnetction类是代表HTTP网络连接的类,由URL类的opentConnetion()方法获得HttpURLConnection对象。HttpURLConnetction可以模拟一个浏览器。发出请求时,设置各种请求头参数、请求参数,传输请求数据;收到响应结果时,获得响应头数据getHeaderFields()和响应内容getInputStream()。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
URL url = new URL(“http://www.baidu.com”);
HttpURLConnection conn = (HttpURLConnection) url.openConnection(); //创建对象
conn.connection(); //打开连接
//打印请求响应的头部
Map<String, List<String>) header = conn.getHeaderFields();
for (String key : header.keySet()) {
System.out.println(key + “header.get(key));
}
//打印响应内容
BufferedReader br = new BufferedReader(new InputStreamReader(
conn.getInputStream(), “UTF-8”));
String str = null;
while((str == br.readLine()) != null) {
System.out.println(str);
}
conn.disconnect(); //断开连接

上面得到头部信息:采用Map类型的key-value格式;得到响应信息:调用连接的getInputStream()方法。

84. Java对数据的支持

具体请见Mysql-2.创建与查询
SQL是结构化的查询语言(structured query language),是专门用于和数据库通信的计算机语言。
1)查询
SELETC <列名> FROM <表名/视图>
WHERE <筛选条件> ORDER BY <列名> <ASC/DESC>
2)更改
插入一行:INSERT INTO t_user VALUES (‘zhangsan’, 30, ‘male’);
插入行的一部分:INSERT INTO t_user (name, age) VALUES (‘zhangsan’, 30);
插入某些查询结果到表:INSERT INTO t_user SELECT *;
UPDATE是对行的某些行或列进行更新。
UPDATE t_user SET gender=’famale’ WHERE name=‘zhangsan’;
DELETE删除行
DELETE FROM t_user WHERE name=’zhangsan’;

85. JDBC的工作原理

Mysql驱动程序的jar包,对于普通的Java程序将jar包放在类搜索路径(CLASSPATH)下;对于Java Web程序将jar包放在WEB_INF/lib文件夹中。
JDBC采用一种驱动模式的设计,提供了两套接口:开发者使用的API和数据库厂商使用的SPI,充分地体现了面向接口编程的好处。
开发者无需关系数据库的连接和调用,值需要使用JDK中提供的标准API编程即可。

86. JDBC操作数据库的步骤

Java提供的关于JDBC的操作接口和类,主要在java.sql和javax.sql包下。
1) 加载驱动程序。具体请见Mysql-1.JDBC连接数据库
Class.forName(“数据库驱动的完成类名”); //通过反射得到Class对象
Mysql的驱动类名为com.mysql.fabric.jdbc.FabricMySQLDriver
2) 获取数据库连接
Connection con = DriverManager.getConnection(“数据库地址”, “用户名”, “密码”);
Mysql的数据库地址格式为:jdbc:mysql://localhost:3306/db_bank
3) 要执行的SQL语句
增、删、改:
String sql1 = “INSERT INTO t_student VALUES (?, ?, ?);
查询:
String sql2 = “SELECT * FROM t_student”;
4)创建会话PreparedStatement(预编译会话)
接口PreparedStatement继承了Statement。
PreparedStatement pstat = conn.prepareStatement(sql1);
5)执行SQL语句
增、删、改:具体请见Mysql-3.PreparedStatement接口和CallableStatement接口
pstat.setString(1, student.getName()); //对第1列设置值其中传入的参数是根据类对象的构造函数决定的,如pstat.setString(1, name);
pstat.setInt(2, getAge());
pstat.setString(3, getGender());
int rst1 = pstat.executeUpdate(sql1); //操作了几条数据就返回几
查询:具体请见Mysql-4.ResultSet结果集、获取元数据
ResultSet rst2 = pstat.executeQury(sql2); //返回结果集
while(rst2.next()){ //ResultSet类的next()方法遍历一行
String name = rst2.getString(“name”); //获取第一列(name)属性的值
int age = rst2.getInt(“age”);
String gender = rst2.getString(“gender”);
System.out.println(“名字:” + name + “年龄:” + age + “性别:” + gender);
}
6) 关闭连接
rst1.close(); //先关结果集
rst2.close();
stat.close(); //关闭会话
conn.close(); //关闭连接

87. 如何使用JDBC事务

具体请见Mysql-6.JDBC事务处理。一个逻辑单位被称为事务,必须具备ACID(原子性(atomicity)、一致性(consistency)、隔离性或独立性(isolation)、持久性(durability))4个属性。
原子性:事务必须是原子工作单元,对于数据的修改,要么全部执行,要不全部不执行。
一致性:在事务完成时,必须使所有的数据都保持一致状态。如A银行转账扣款100元,则B银行的账号一定增加100元。
隔离性:并发事务所做的修改必须与其他并发事务所做的修改隔离。如A和B同时为C转账,一定不能同时对C的余额进行修改。
持久性:事务完成后,它对系统的影响是永久的。该修改即使出现致命的系统故障也会一直保持。
事务的结果只能有两种形式:提交和回滚(rollback)。若操作完全成功则提交,产生永久性的修改(JDBC默认自动提交);若操作不完全成功则回滚,恢复到事务开始前的状态。
(1)关闭自动提交事务,连接的自动提交事务属性设置为false。

1
2
Connection conn = DriverManager.getConnection(“数据库地址”, “用户名”,”密码”);
conn.setAutoCommit(false);

(2)捕获(try…catch)执行代码,如果执行过程顺利则提交;一旦发生异常就回滚(rollback)事务。

1
2
3
4
5
6
7
8
9
10
11
12
13
try{
Connction conn = DriverManager.getConnection(“数据库地址”, “用户名”,”密码”);
conn.setAutoCommit(false);
PreparedStatement pstat = conn.prepareStatement(sql);
pstat.setString(1, name);
pstat.setInt(2, age);
pstat.setString(3, gender);
ptat.executeUpdata();
conn.commit(); //提交事务
} catch(Exception e){
e.printStackTrace();
conn.rollback(); //回滚事务
}

(3)关闭连接
一般放在finally块中。

1
2
3
4
5
6
7
8
finally{
if (pstat != null){
pstat.close()
}
if (conn != null){
conn.close();
}
}

88. 如何使用JDBC实现数据访问对象层

DAO(data access object,数据访问对象层)做的主要工作就是把数据包装成对象和把对象拆分成数据。增、删、改、查是DAO实现的基本操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class User {
private long id;
private String name;
private String gender;
public User() {
super();
}
public User(long id, String name, String gender) {
super();
this.id = id;
this.name = name;
this.gender = gender;
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;

public class JDBCDao {
private static String jdbcName = "com.mysql.fabric.jdbc.FabricMySQLDriver"; // 驱动名称
private static String dbUrl = "jdbc:mysql://localhost:3306/db_bank";// 数据库地址
private static String dbUserName = "root"; // 数据库用户名
private static String dbPassword = "123456"; // 数据库密码
// 加载驱动
static{
try{
Class.forName(jdbcName);
} catch(ClassNotFoundException e) {
e.printStackTrace();
}
}

//获取连接
private Connection getConn() {
try{
return DriverManager.getConnection(dbUrl, dbUserName, dbPassword);
} catch(SQLException e) {
e.printStackTrace();
}
return null;
}

// 释放资源
private void close(ResultSet rs, Statement ps, Connection conn) {
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
if (ps != null) {
try {
ps.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}

//用Id获取用户对象
public User getUserById(long id) {
Connection conn = null;
ResultSet rs = null;
PreparedStatement ps = null;
String sql = "SELCTE * FROM t_user WHERE id = ?";
try{
conn = this.getConn();
ps = conn.prepareStatement(sql);
ps.setLong(1, id);
rs = ps.executeQuery();
if(rs.next()) {
//如果这个用户存在,则直接构建并返回
User user = new User(rs.getLong("id"), rs.getString("name"), rs.getString("gender"));
return user;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
this.close(rs, ps, conn);
}
return null;
}
//查询所有用户
public List<User> getAllUsers() {
//集合是用于存储其它对象的对象
//将所有的用户对象存放到List类型的集合中
List<User> list = new ArrayList<>();
Connection conn = null;
ResultSet rs = null;
PreparedStatement ps = null;
String sql = "SELCTE * FROM t_user";
try{
conn = this.getConn();
ps = conn.prepareStatement(sql);
rs = ps.executeQuery();
//循环添加所有用户
while (rs.next()) {
User user = new User(rs.getLong("id"), rs.getString("name"), rs.getString("gender"));
list.add(user);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
this.close(rs, ps, conn);
}
return null;
}
/**
* 修改用户数据
* @param user 需要修改的用户
* @return 用户对象
*/
public User updateUser(User user) {
Connection conn = null;
PreparedStatement ps = null;
String sql = "UPDATE t_user SET id=?, name=?,gender=?";
try{
conn = this.getConn();
conn.setAutoCommit(false); //设置自动提交为false
ps = conn.prepareStatement(sql);
ps.setLong(1, user.getId());
ps.setString(2, user.getName());
ps.setString(3, user.getGender());
int rst = ps.executeUpdate();
if (rst > 0) {
return user;
}
conn.commit(); //如果修改成功,提交事务
} catch (Exception e) {
e.printStackTrace();
try{
conn.rollback(); //如果出错,则回滚视乎
} catch (SQLException se) {
se.printStackTrace();
}
} finally {
this.close(null, ps, conn);
}
return null;
}

//根据Id删除用户

//插入用户数据

}

89. 如何使用连接池

若操作数据库的程序比较多,频繁地创建数据库和建立连接是非常耗费资源的,因此提出了数据库连接技术。
数据库连接池中装的是数据库的连接(Connection),当需要连接数据库时,只需要从池子中取出即可。当调用Connection.close()方法时,连接就返回到池子,但并没有真正与数据库断开连接。当连接不够时,它会创建一些连接;当连接太多时,会自动关闭一些不必要的连接。
数据库连接池工作原理
数据库连接池往往作为一个单独的程序模块进行运行,由他来维护这些连接。开发者可以配置它的一些属性,最大活动连接数、最大空闲连接数、连接超时等。它与传统的JDBC提供连接的方式不太一样,必须使用数据源(Data Source)的形式获取连接,数据源对象往往是以JNDI(Java Naming and Directory Interface,Java命名与目录接口)的形式提供给开发者。数据连接池提供商对连接池的实现各不相同,但它们都必须实现javax.sql.DataSource接口,开发者只需要面向这些接口编程就好了。传统JDBC通过驱动管理器(DriverManager)来获取连接;而连接池则需要用数据源(DataSource)来获取。
1)创建JDNI初始化上下文对象
InitialContext cxt = new InitialContext();
2)通过JNDI上下文获取到的数据源
DataSource ds = (DataSource) cxt.lookup(“数据源在JNDI上的路径”);
3)通过数据源获取到对象
Connection conn = ds.getConnection();

90. 如何使用可滚动的结果集

一般来说,使用ResultSet类的next()方法可以迭代地遍历结果集中的所有的行(可理解为上下移动),但有时希望结果集可以前后移动。JDBC从JDBC2.0开始支持可滚动的结果集(ResultS),可以在结果上前后移动,并且可以跳转到结果集中的任何位置。
在创建会话对象的时候进行指定:
PreparedStatement pstat = conn.prepareStatement (sql, type, concurrency);
其中
1)type用来设置是否为可滚动结果集,它为ResultSet的静态变量:
TYPE_FORWARD_ONLY:结果集不能滚动。
TYPE_SCROLL_INSENSITIVE:结果集可以滚动,但对数据库变化不敏感,数据库查询生成结果集后发生了变化,结果集不发生变化。
TYPE_SCROLL_SENSITIVE:结果集可以滚动,但对数据库变化敏感。
2)concurrency用于指定是否为可更新的结果集,它也为ResultSet的静态变量。
直接在查询结果里修改数据,并同步到数据库。
CONCUR_READ_ONLY:结果集不能用于更新数据库。
CONCUR_UPDATABLE:结果集可以用于更新数据库。
实际上,数据库驱动可能无法支持可滚动或可更新结果集的请求。通过DatabaseMetaData类中的boolean SupportsResultSetType(int type)和boolean SupportsResultSetConcurrency(int type, int concurrency)可以知道某个数据库究竟支持哪些结果集类型以及那些模式。